/*
    Borealis Library
    A library of commonly used hydrological modeling building blocks

    Copyright (C) 2008  Chris Marsh

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "include\borealis\ascii_raster.hpp"


namespace Borealis
{

	void ascii_raster::threaded_parse::operator()(const tbb::blocked_range<unsigned int>& r) const
	{
		using namespace boost::spirit::classic;

		std::string* lines = m_lines;
		bool* failure = m_failure;
		std::string* msg = m_msg;
		tbb::concurrent_vector<float>* array = m_array;
		*failure = false;

		//contains the parsed numbers
		std::vector<float> items;
		items.reserve(m_cols);
		
		real_parser<float,real_parser_policies<float> > float_p;

		for(unsigned int row = r.begin(); row < r.end(); row++)
		{
			//current col
			unsigned int col = 0;

	
			 parse(m_lines[row].begin(),
                m_lines[row].end(),
				//BEGIN GRAMMAR
				*space_p >> float_p[push_back_a(items)] >> *(+space_p >> float_p[push_back_a(items)]) >> *space_p
				//END GRAMMAR
				);
			 //re-enables the warning
			 //here there is "apparently" a cast from double to float
			 //we know this, so just stfu compiler
			#pragma warning(default: 4244)
			 
			//report an error
			if(items.size() != m_cols)
			{
				*failure = true;
				*msg = "Not enough columns at line " + boost::lexical_cast<std::string>(row+1);
				return;
			}

			//need to chance to tbb:vector if we want to parallelize this
			for(unsigned int col = 0; col < items.size(); col++)
			{
				//array->push_back(items[col]);
				array->operator [](row*m_cols+col) = items[col];
			}


			items.clear();
		}

	}



	ascii_raster::~ascii_raster()
	{

	}

	void ascii_raster::open( std::string path )
	{
		std::fstream file(path.c_str());
		std::string line = "";
		int rows = 0;
		int cols = 0;

 		if(!m_array.empty())
 		{
 			//clear current array
			m_array[0].clear();
 			m_array.clear();
 		}

		if(!file.is_open())
			throw std::runtime_error(std::string("Unable to open file ") + path);

		//begin to read in the header in the format
		// 		ncols             1674
		// 		nrows             1213
		// 		xllcorner     547502.0
		// 		yllcorner     7617226.0
		// 		cellsize         10.0
		// 		NODATA_value   -9999.0
		regex_tokenizer token;
		std::vector<std::string> header;

		//try to read the first line (ncols)
		token.set_regex("ncols[[:space:]]+[0-9]+",false);
		getline(file,line);

		header = token.tokenize<std::string>(line);

		if(header.size() != 1)
			throw std::runtime_error("Not a valid ArcGis ASCII file - missing ncols");

		//our first line looks like it is the start of the header so try to read the rest
		token.set_regex("[0-9]+");
		header = token.tokenize<std::string>(header[0]);

		try
		{
			cols = boost::lexical_cast<int>(header[0]);
		}
		catch (boost::bad_lexical_cast e)
		{
			throw std::runtime_error("Failed to read the col count");
		}

		//try to read nrows
		token.set_regex("nrows[[:blank:]]+[0-9]+",false);
		getline(file,line);

		header = token.tokenize<std::string>(line);

		if(header.size() != 1)
			throw std::runtime_error("Not a valid ArcGis ASCII file - missing nrows");

		//our  line looks like it is the start of the header so try to read the rest
		token.set_regex("[0-9]+");
		header = token.tokenize<std::string>(header[0]);

		try
		{
			rows = boost::lexical_cast<int>(header[0]);
		}
		catch (boost::bad_lexical_cast e)
		{
			throw std::runtime_error("Failed to read the row count");
		}

		m_rows = rows;
		m_cols = cols;

		//we now have our size, make our array;
		m_array.grow_by(1);
		m_array[0].grow_by(m_rows*m_cols);

		token.set_regex("xllcorner[[:blank:]]+-?[0-9]+\\.?[0-9]*",false);
		getline(file,line);

		header = token.tokenize<std::string>(line);

		if(header.size() != 1)
			throw std::runtime_error("Not a valid ArcGis ASCII file - missing xllcorner");

		//contains what we want
		token.set_regex("-?[0-9]+\\.?[0-9]*");
		header = token.tokenize<std::string>(header[0]);

		try
		{
			m_xllCorner = boost::lexical_cast<float>(header[0]);
		}
		catch (boost::bad_lexical_cast e)
		{
			throw std::runtime_error("Failed to read the xllcorner");
		}

		token.set_regex("yllcorner[[:blank:]]+-?[0-9]+\\.?[0-9]*",false);
		getline(file,line);

		header = token.tokenize<std::string>(line);

		if(header.size() != 1)
			throw std::runtime_error("Not a valid ArcGis ASCII file - missing yllcorner");

		token.set_regex("-?[0-9]+\\.?[0-9]*");
		header = token.tokenize<std::string>(header[0]);

		try
		{
			m_yllCorner = boost::lexical_cast<float>(header[0]);
		}
		catch (boost::bad_lexical_cast e)
		{
			throw std::runtime_error("Failed to read the yllcorner");
		}

		token.set_regex("cellsize[[:blank:]]+[0-9]+",false);
		getline(file,line);

		header = token.tokenize<std::string>(line);

		if(header.size() != 1)
			throw std::runtime_error("Not a valid ArcGis ASCII file - missing cellsize");

		token.set_regex("[0-9]+");
		header = token.tokenize<std::string>(header[0]);

		try
		{
			m_cellSize = boost::lexical_cast<int>(header[0]);
		}
		catch (boost::bad_lexical_cast e)
		{
			throw std::runtime_error("Failed to read the cellsize");
		}

		token.set_regex("NODATA_value[[:blank:]]+-?[0-9]+\\.?[0-9]*",false);
		getline(file,line);

		header = token.tokenize<std::string>(line);

		if(header.size() != 1)
			throw std::runtime_error("Not a valid ArcGis ASCII file - missing NODATA_value");

		token.set_regex("-?[0-9]+\\.?[0-9]*");
		header = token.tokenize<std::string>(header[0]);

		try
		{
			m_noData = boost::lexical_cast<float>(header[0]);
		}
		catch (boost::bad_lexical_cast e)
		{
			throw std::runtime_error("Failed to read the NODATA_value");
		}

		//need to skip any whitespace lines after the header and before the actual data 
		//therefore skip any line that fails the number regex

		//will match either an integer or floating point number, possible negative 
		token.set_regex("-?[0-9]+\\.?[0-9]*");
		bool done = false;
		while(!done)
		{
			getline(file,line);
			header = token.tokenize<std::string>(line);

			//skip any line that is a blank, or that has text in it
			if (header.size() != 0)
				done = true;

		} 

		//read in the rest of the file
		std::string* restOfFile = new std::string[m_rows];
		unsigned int r=0;
		
		//need a do here as the above will have put a valid line in 'line'
		do
		{
			if(!line.empty())
			 restOfFile[r++] = line;
		}while(getline(file,line) && r<m_rows);
		
		file.close();


		//do the iterations
		bool* failure = new bool;
		*failure = false;
		std::string* msg = new std::string;

		tbb::blocked_range<unsigned int> range(0,m_rows);

		tbb::parallel_for(range,threaded_parse(restOfFile,&m_array[0],m_cols,failure,msg),tbb::auto_partitioner());

		if(*failure)
		{
			throw std::runtime_error(msg->c_str());
		}
		

		delete[] restOfFile;
		delete failure;
		delete msg;
		
	}


	
	void ascii_raster::to_file(std::string outFile)
	{
		if(m_array.empty())
		{
			throw std::runtime_error("No raster in memory");
		}
		std::ofstream out;
		out.open(outFile.c_str());

		if(!out.is_open())
			throw std::runtime_error("Unable to open output file");

		out << std::fixed;
		out << "ncols\t" << m_cols << std::endl;
		out << "nrows\t" << m_rows << std::endl;
		out << "xllcorner\t" << m_xllCorner << std::endl;
		out << "yllcorner\t" << m_yllCorner << std::endl;
		out << "cellsize\t" << m_cellSize << std::endl;
		out << "NODATA_value\t" << m_noData << std::endl;
		out <<  std::endl;

		for(unsigned int i = 0; i < m_rows; i++)
		{
			for(unsigned int j = 0; j < m_cols; j++)
			{
				out << m_array[0][i*m_cols+j] << "    ";
			}
			out << std::endl;
		}
	}

	ascii_raster::ascii_raster(unsigned int row, unsigned int col)
	{
		
		create_empty_raster(row,col);
		m_noData = -9999.0F;
	}

	ascii_raster::ascii_raster() : 
		raster()
	{
	
		m_noData = -9999.0F;

	}

	ascii_raster::ascii_raster( ascii_raster& in ) :
		raster(in)
	{
		
		m_cellSize = in.get_cell_size();
		m_noData = in.get_nodata();
		m_xllCorner = in.get_xll_corner();
		m_yllCorner = in.get_yll_corner();

	}

	Borealis::ascii_raster& ascii_raster::operator=( const ascii_raster& rhs )
	{

		if(this == &rhs)
			return *this;

		raster::operator=(rhs);

		m_cellSize = rhs.get_cell_size();
		m_noData = rhs.get_nodata();
		m_xllCorner = rhs.get_xll_corner();
		m_yllCorner = rhs.get_yll_corner();

		return *this;
	}

	void ascii_raster::to_xyzfile(std::string outFile)
	{
		if(m_array.empty())
		{
			throw std::runtime_error("No raster in memory");
		}
		std::ofstream out;
		out.open(outFile.c_str());

		if(!out.is_open())
			throw std::runtime_error("Unable to open output file");

		for(unsigned int i = 0; i < m_rows; i++)
		{
			for(unsigned int j = 0; j < m_cols; j++)
			{
				//     x            y				z
				out << i << "\t" << j << "\t" << m_array[0][i*m_cols+j] << std::endl;
			}
		}

		out.close();
	}

	void ascii_raster::scale_by(float scaleFactor)
	{
		float factor = 0.0;

		if(m_array.empty())
			throw std::runtime_error("No raster in memory");

		//do the iterations
		tbb::blocked_range2d<unsigned int> range(0,m_rows,0,m_cols);
		tbb::parallel_for(range,threaded_apply_scale_by(scaleFactor,m_rows,m_cols,&m_array[0]));


	}

	float ascii_raster::get_nodata() const { return m_noData;}




}